package hn.sigit.logic.viewer.toolbox;

import hn.sigit.logic.bpm.AttachedFileData;
import hn.sigit.logic.general.ResourceBundleHelper;
import hn.sigit.logic.geometry.BearingAndDistance;
import hn.sigit.logic.geometry.GeometryOperations;
import hn.sigit.logic.viewer.InteractiveViewerHelper;
import hn.sigit.util.ShapeFile;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;

import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.remoting.WebRemote;
import org.richfaces.event.UploadEvent;
import org.richfaces.model.UploadItem;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.io.WKTWriter;

@Name("borderHelper")
@Scope(value=ScopeType.CONVERSATION)
@AutoCreate
public class BorderHelper {
	protected final InteractiveViewerHelper interactiveViewerHelper;
	protected final ResourceBundleHelper resBundle;
	
	private List<Coord> currentBorder;
	private List<BearingAndDistance> bearingAndDistances;
	private Coord borderPoint;
	
	//METHOD 2: Variables
	private Coord refPoint;
	private Coord resultingPoint;
	
	private BearingDistance bearingDistance;
	//END METHOD 2 Variables
	
	private AttachedFileData selectedFile;
	private ViewerCallable viewerCallable;

	
	public BorderHelper(InteractiveViewerHelper interactiveViewerHelper) {
		this.interactiveViewerHelper = interactiveViewerHelper;
		this.resBundle = interactiveViewerHelper.getResBundle();
	}

	public List<Coord> getCurrentBorder() {
		if (currentBorder == null)
			currentBorder = new ArrayList<Coord>();
		return currentBorder;
	}
	public void setCurrentBorder(List<Coord> currentBorder) {
		if (this.currentBorder != currentBorder) {
			this.currentBorder = currentBorder;
			setBearingAndDistances(null);
		}
	}
	
	public List<BearingAndDistance> getBearingAndDistances() {
		if (bearingAndDistances == null) {
			bearingAndDistances = new ArrayList<BearingAndDistance>();

			if (getCurrentBorder().size() > 0) {
				if (getRefPoint().isSet()) {
					Coord c = getCurrentBorder().get(0);
					bearingAndDistances.add(
							BearingAndDistance.newFromCoords(getRefPoint().x, getRefPoint().y, c.x, c.y));
				}
				
				for (int i = 0; i < getCurrentBorder().size() - 1; i++) {
					Coord c1 = getCurrentBorder().get(i);
					Coord c2 = getCurrentBorder().get(i+1);
					bearingAndDistances.add(
							BearingAndDistance.newFromCoords(c1.x, c1.y, c2.x, c2.y));
				}
			}
		}
		return bearingAndDistances;
	}
	public void setBearingAndDistances(List<BearingAndDistance> bearingAndDistances) {
		this.bearingAndDistances = bearingAndDistances;
	}
	
	
	public Coord getBorderPoint() {
		if (borderPoint == null)
			borderPoint = new Coord();
		return borderPoint;
	}
	public void setBorderPoint(Coord BorderPoint) {
		this.borderPoint = BorderPoint;
	}

	public Coord getRefPoint() {
		if (refPoint == null)
			refPoint = new Coord();
		return refPoint;
	}
	public void setRefPoint(Coord refPoint) {
		this.refPoint = refPoint;
	}

	public Coord getResultingPoint() {
		if (resultingPoint == null)
			resultingPoint = new Coord();
		return resultingPoint;
	}
	public void setResultingPoint(Coord resultingPoint) {
		this.resultingPoint = resultingPoint;
	}

	public BearingDistance getBearingDistance() {
		if (bearingDistance == null)
			bearingDistance = new BearingDistance();
		return bearingDistance;
	}
	public void setBearingDistance(BearingDistance bearingDistance) {
		this.bearingDistance = bearingDistance;
	}

	public Coord getStartingPoint() {
		if (getRefPoint().isSet())
			return getRefPoint();
		else if (getCurrentBorder().size() > 0)
			return getCurrentBorder().get(0);
		
		return new Coord();
	}
	

	public String addPointToBoundary() {
		return addPointToBoundary(null);
	}
	public String addPointToBoundary(Long maxNumPoints) {
		Coord coord = new Coord(borderPoint);
		getCurrentBorder().add(coord);
		
		if (maxNumPoints != null && maxNumPoints > 0) {
			while (getCurrentBorder().size() > maxNumPoints)
				getCurrentBorder().remove(0);
		}

		setBearingAndDistances(null);

		return null;
	}
	public String setBoundaryPoint(int index) {
		Coord coord = new Coord(borderPoint);
		if (index < getCurrentBorder().size())
			getCurrentBorder().set(index, coord);
		else {
			while (getCurrentBorder().size() <= index)
				getCurrentBorder().add(coord);
		}
		
		setBearingAndDistances(null);

		return null;
	}
	public String addBearingDistanceToBoundary() {
		final int BorderSize = getCurrentBorder().size();
		
		try {
			if (BorderSize > 0) {
				double angle = GeometryOperations.directionToRadAngle(bearingDistance.getBearing());
				double distance = bearingDistance.getDistance();
				
				double dx = distance * Math.cos(angle);
				double dy = distance * Math.sin(angle);
				
				Coord lastCoord = getCurrentBorder().get(BorderSize - 1);
				
				Coord newCoord = new Coord(lastCoord.x + dx, lastCoord.y + dy);
				getCurrentBorder().add(newCoord);
				
				setBearingAndDistances(null);
			}
			else {
				FacesContext.getCurrentInstance().addMessage("",
						new FacesMessage(FacesMessage.SEVERITY_ERROR,
								"Error agregando nuevo punto a la frontera",
								"No existe punto de partida para el nuevo rumbo y distancia"));
			}
		}
		catch (IllegalArgumentException e) {
			FacesContext.getCurrentInstance().addMessage("",
					new FacesMessage(FacesMessage.SEVERITY_ERROR,
							"Error de ingreso de datos",
							"El dato de rumbo y distancia ingresado no es correcto"));
		}
		
		return null;
	}
	public String deleteLastBoundaryPoint() {
		List<Coord> coordList = getCurrentBorder();
		if (coordList.size() > 0) {
			coordList.remove(coordList.size() - 1);
			getBearingAndDistances().remove(getBearingAndDistances().size() - 1);
		}
		return null;
	}
	public String clearBoundary() {
		getCurrentBorder().clear();
		getBearingAndDistances().clear();
		setBorderPoint(null);
		setRefPoint(null);
		setResultingPoint(null);
		setBearingDistance(null);

		return null;
	}
	
	public String applyRefOffset() {
		switch (getCurrentBorder().size()) {
		case 0:
			getCurrentBorder().add(new Coord(resultingPoint));
			break;
		case 1:
			Coord coord = getCurrentBorder().get(0);
			coord.setEqualTo(resultingPoint);
			break;
		default:
			Coord firstCoord = getCurrentBorder().get(0);
			double dx = resultingPoint.x - firstCoord.x;
			double dy = resultingPoint.y - firstCoord.y;
			
			for (Coord c : getCurrentBorder()) {
				c.x += dx;
				c.y += dy;
			}
			break;
		}

		return null;
	}
	
	
	
	

	@WebRemote
	public String getBorderWKT() {
		int borderSize = getCurrentBorder().size();
		
		if (borderSize > 1) {
			WKTWriter wktWriter = new WKTWriter();
			Coord c;
			Coordinate[] coordinates = new Coordinate[borderSize];
			for (int i = 0; i < coordinates.length; i++) {
				c = currentBorder.get(i);
				coordinates[i] = new Coordinate(c.getX(), c.getY());
			}

			LineString ls = GeometryOperations.geomFactory.createLineString(coordinates);

			return wktWriter.write(ls);
		}

		return "";
	
	}

	public AttachedFileData getSelectedFile() {
		return selectedFile;
	}
	public void setSelectedFile(AttachedFileData selectedFile) {
		this.selectedFile = selectedFile;
	}
	
	public ViewerCallable getViewerCallable() {
		return viewerCallable;
	}
	public void setViewerCallable(ViewerCallable viewerCallable) {
		this.viewerCallable = viewerCallable;
	}

	public void attachFileListener(UploadEvent event) {
		UploadItem item = event.getUploadItem();
		
		try {
			File file = item.getFile();
			String fname = item.getFileName().toUpperCase();
			if (fname.endsWith(".TXT") || fname.endsWith(".SHP")) {
				String ext = fname.substring(fname.lastIndexOf('.'));
				File renamedFile = new File(file.getCanonicalPath() + ext);
				
				if (file.renameTo(renamedFile))
					file = renamedFile;
				else
					throw new IllegalArgumentException("No se pudo abrir el archivo subido al sistema");
				
				AttachedFileData afd = new AttachedFileData();
				afd.setFileName(fname);
				afd.setFile(file);
				
				setSelectedFile(afd);
			}
			else
				throw new IllegalArgumentException("Solamente se aceptan archivos con extension TXT o SHP");
			
			
			file.deleteOnExit();
		}
		catch (Throwable t) {
			FacesContext.getCurrentInstance().addMessage("",
					new FacesMessage(FacesMessage.SEVERITY_ERROR,
							"Error al intentar procesar el archivo " + selectedFile.getFileName() + ": ",
							t.getMessage()));
			t.printStackTrace();
		}
	}

	public void uploadNewFile() {
		setSelectedFile(null);
	}
	public void acceptSelectedFile() {
		if (getSelectedFile() == null) return;
		
		String fname = selectedFile.getFileName().toUpperCase();
		if (fname.endsWith(".TXT"))
			processTextFile();
		else if (fname.endsWith(".SHP"))
			processShapeFile();
		
		setSelectedFile(null);
	}
	
	private void processTextFile() {
		Scanner scanner;
		try {
			scanner = new Scanner(selectedFile.getFile());
			scanner.useDelimiter(",");
			
			String line, northing, easting;
			String[] neLine;
			double x, y;
			int nidx, eidx;
			
			if (scanner.hasNext()) {
				getCurrentBorder().clear();
				do {
					line = scanner.next();
					neLine = line.split(" ", 2);
					
					northing = neLine[0];
					easting = neLine[1];
					
					nidx = northing.indexOf('N');
					eidx = easting.indexOf('E');
					
					try {
						y = nidx != -1 ? Double.parseDouble(northing.substring(0, nidx)) : Double.parseDouble(northing); 
						x = eidx != -1 ? Double.parseDouble(easting.substring(0, eidx)) : Double.parseDouble(northing);

						getCurrentBorder().add(new Coord(x, y));
					}
					catch (NumberFormatException e) {
						e.printStackTrace();
					}
				} while (scanner.hasNext());
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}
	
	private void processShapeFile() {
		ShapeFile sf = new ShapeFile(null, selectedFile.getFile());
		if (sf.readShapefile()) {
			getCurrentBorder().clear();
			for (Geometry g : sf.getGeometries()) {
				if (g instanceof LineString) {
					for (Coordinate c : g.getCoordinates()) {
						getCurrentBorder().add(new Coord(c.x, c.y));
					}
					break;
				}
				else if (g instanceof MultiLineString) {
					//we will only consider the first LineString
					MultiLineString mls = (MultiLineString) g;
					if (mls.getNumGeometries() > 0) {
						LineString ls = (LineString) mls.getGeometryN(0);
						for (Coordinate c : ls.getCoordinates()) {
							getCurrentBorder().add(new Coord(c.x, c.y));
						}
					}
				}
				else if (g instanceof Point) {
					Point p = (Point) g;
					getCurrentBorder().add(new Coord(p.getX(), p.getY()));
				}
				else if (g instanceof MultiPoint) {
					//we will consider the first point only
					MultiPoint mp = (MultiPoint) g;
					if (mp.getNumGeometries() > 0) {
						Point p = (Point) mp.getGeometryN(0);
						getCurrentBorder().add(new Coord(p.getX(), p.getY()));
					}
				}
			}
			
			if (getCurrentBorder().size() == 0)
				FacesContext.getCurrentInstance().addMessage("",
						new FacesMessage(FacesMessage.SEVERITY_ERROR,
								resBundle.loadMessage("dataentry.split.uploaded_file_error") + ": ",
								resBundle.loadMessage("dataentry.split.no_geometries_in_shp")));
		}
		else {
			FacesContext.getCurrentInstance().addMessage("",
					new FacesMessage(FacesMessage.SEVERITY_ERROR,
							resBundle.loadMessage("dataentry.split.uploaded_file_error") + ": ",
							resBundle.loadMessage("dataentry.split.cant_read_file") + ' ' + selectedFile.getFileName()));
		}
	}
	
	public String doOperation() {
		try {
			if (viewerCallable != null) {
				viewerCallable.doBorderOperation(interactiveViewerHelper);
			}
		}
		catch (Exception e) {
			FacesContext.getCurrentInstance().addMessage("",
					new FacesMessage(FacesMessage.SEVERITY_ERROR,
							resBundle.loadMessage("dataentry.split.border_operation_error") + ": ",
							e.getMessage()));
		}
		return null;
	}
	

	public static class Coord {
		private double x;
		private double y;
		private boolean set;
		
		public Coord() {
			this.set = false;
		}
		
		public Coord(double x, double y) {
			this();
			
			setX(x);
			setY(y);
		}
		
		public Coord(Coord other) {
			setX(other.getX());
			setY(other.getY());
		}
		
		public double getX() {
			return x;
		}
		public void setX(double x) {
			this.x = x;
			set = true;
		}
		
		public double getY() {
			return y;
		}
		public void setY(double y) {
			this.y = y;
			set = true;
		}
		
		public void setEqualTo(Coord other) {
			setX(other.getX());
			setY(other.getY());
		}

		public boolean isSet() {
			return set;
		}
		public void setSet(boolean set) {
			this.set = set;
		}
	}
	
	public static class BearingDistance {
		private String bearing;
		private double distance;
		
		public String getBearing() {
			return bearing;
		}
		public void setBearing(String bearing) {
			this.bearing = bearing;
		}
		public double getDistance() {
			return distance;
		}
		public void setDistance(double distance) {
			this.distance = distance;
		}
	}

}
